#include "structEdgeBase.hpp"
#include "meshTypes.hpp"
#include "structMeshFunctions.hpp"
#include "exceptions.hpp"

namespace hms {


template<typename EdgeType>
StructEdgeBase<EdgeType>::StructEdgeBase( const MeshType& mesh, Index index ) :
	StructEdgeBase( mesh, index, whichRange(mesh, index) )
{}

template<typename EdgeType>
StructEdgeBase<EdgeType>::StructEdgeBase(
	const MeshType& mesh, Index index, std::pair<EdgeRange,Index> range
) :
	m_mesh     { mesh },
	m_index    { index },
	m_modIndex { range.second },
	m_range    { range.first }
{}

template<typename EdgeType>
auto StructEdgeBase<EdgeType>::meshImp() const -> const MeshType& {
	return m_mesh;
}

template<typename EdgeType>
auto StructEdgeBase<EdgeType>::indexImp() const -> Index {
	return m_index;
}

template<typename EdgeType>
auto StructEdgeBase<EdgeType>::modIndex() const -> Index {
	return m_modIndex;
}

template<typename EdgeType>
auto StructEdgeBase<EdgeType>::range() const -> EdgeRange {
	return m_range;
}


template<typename EdgeType>
auto StructEdgeBase<EdgeType>::verticesImp   () const -> VertexIndices {
	if (m_range == EdgeRange::horizontal)
		return Array2i{0,1} + m_index + m_index / m_mesh.nCellsX();
	else
		return Array2i{0,m_mesh.nCellsX()+1} + m_modIndex;
}

template<typename EdgeType>
auto StructEdgeBase<EdgeType>::isBoundaryImp () const -> bool {
	if (m_range == EdgeRange::horizontal)
		return
			m_index <  m_mesh.nCellsX() ||
			m_index >= m_mesh.nCellsX() * m_mesh.nCellsY();
	else
		return
			(m_modIndex+1) % (m_mesh.nCellsX()+1) == 1 ||
			(m_modIndex+1) % (m_mesh.nCellsX()+1) == 0;
}

template<typename EdgeType>
auto StructEdgeBase<EdgeType>::cellsImp(Index order) const -> CellIndices {

#ifdef STRUCTMESH_PRECALC_EDGE_PARENT_CELLS
	return m_mesh.edges().cells(order).col(m_index);
#else
	static constexpr Index ng {1};
	const Index
		& nx { m_mesh.nCellsX() },
		& ny { m_mesh.nCellsX() };
	if (m_range == EdgeRange::horizontal)
		return
			(nx + 2*ng) *
			( ng + m_index / nx + Array2i{-order, order-1} )
			+ ng + m_index % nx;
	else
		return
			Array2i{-order, order-1} + ng
			+ (m_modIndex / (nx+1) + ng) * (nx + 2*ng)
			+  m_modIndex % (nx+1);

#endif
}

template<typename EdgeType>
auto StructEdgeBase<EdgeType>::domainCellImp(Index order) const -> Index {
	assert( this->isBoundary() && "Called domainCell() on a non-boundary edge!" );
	if (m_range == EdgeRange::horizontal){
		if ( m_index < m_mesh.nCellsX() )
			return this->cells(order)[1];
		else
			return this->cells(order)[0];
	} else {
		if (m_modIndex % (m_mesh.nCellsX()+1) == 0)
			return this->cells(order)[1];
		else
			return this->cells(order)[0];
	}
}

template<typename EdgeType>
auto StructEdgeBase<EdgeType>::ghostCellImp(Index order) const -> Index {
	assert( this->isBoundary() && "Called ghostCell() on a non-boundary edge!" );
	return this->domainCell(order) == this->cells(order)[0] ? 
		this->cells(order)[1] : 
		this->cells(order)[0];
}


template class StructEdgeBase<UniEdge>;
template class StructEdgeBase<RectEdge>;
template class StructEdgeBase<StructEdge>;

}